package cn.edu.pku.ss.situationperceiver;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.alljoyn.bus.BusException;
import org.alljoyn.bus.BusObject;
import org.alljoyn.bus.ErrorReplyBusException;
import org.alljoyn.bus.SignalEmitter;
import org.alljoyn.bus.Status;
import org.alljoyn.bus.annotation.BusSignalHandler;
import org.alljoyn.cops.peergroupmanager.BusObjectData;
import org.alljoyn.cops.peergroupmanager.PeerGroupListener;
import org.alljoyn.cops.peergroupmanager.PeerGroupManager;

import cn.edu.pku.ss.situationperceiver.ContextData.ContextType;


import android.app.Service;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.text.format.Time;
import android.util.Log;

public class AllJoynService extends Service {
	/**
	 * This class implements all networking methods based on AllJoyn.
	 * Responsibilities: 
	 * 1) Connect and Disconnect to the bus. 
	 * 2) JOIN, QUIT, or CREATE a session(group) on the bus. 
	 * 3) Implement update methods of the UI on the main activity. 
	 * 4) Listen to the ContextMessage and ContextSharingRequest on the bus, 
	 *    receive (including check recipient) and deliver it to the 
	 *    ContextShareManager when it arrives. (updated May 23,2013)
	 */

	private static final String TAG = "sp.AllJoynService";

	// service name constant
	private static final String GROUP_PREFIX = "cn.edu.pku.ss.situationperceiver";
	private static final String GROUP_NAME = "contextsharing";

	// peer state enum
	public static enum PeerConnectedState {
		DISCONNECTED, 	/** Not connected to the group */
		SESSION_HOLDER, /** This peer created the group */
		CONNECTED, 		/** This peer is connected to the group */
		DISCOVERING, 	/** The bus attachment is discovering the group */
		SESSION_LOST, 	/** The client lost the group or holder quit */
		JOINED,			/** This peer is joined to the group */
	}

	// Control the discovery progress on the activity
	public static enum ControlDiscoveryProgressInstruction {
		START_PROGRESS_DIALOG, STOP_PROGRESS_DIALOG
	}

	/**
	 * Value for the HANDLE_RECEIVE_INTEREST case observer notification handler.
	 */
	private static final int HANDLE_RECEIVE_INTEREST = 0;

	/**
	 * Value for the HANDLE_SHARE_CONTEXT case observer notification handler.
	 */
	private static final int HANDLE_SHARE_CONTEXT = 1;
	

	/**
	 * Value for the mainActivity to request the service to send context message.
	 */
	public static final int REQUEST_SEND_MESSAGE =2;

	// The flag to turn off the advertised listener
	private boolean mIsAClient = true;

	// The flag to stop the thread if server is found
	private boolean mIsFoundServer = false;

	private Thread mTimerThread;

	// The state of this peer.
	private PeerConnectedState mPeerConnectedState = PeerConnectedState.DISCONNECTED;

	private PeerGroupManager mPeerGroupManager;

	// Service Object (for holder)
	private ContextShareService mContextShareService;
	// Service Interface (for connector)
	private ContextShareInterface mContextShareInterface;
	
	private SignalEmitter mSignalEmitter;

	/* Handler used to make calls to AllJoyn methods. See onBind(). */
	private Handler mBusHandler;

	// Handler
	private Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case HANDLE_RECEIVE_INTEREST: {
				ContextSharingRequest csr = (ContextSharingRequest)msg.obj;
				showRequestMessage(csr);
				
				break;
			}
			case HANDLE_SHARE_CONTEXT: {
				break;
			}
			
			case REQUEST_SEND_MESSAGE:{ 
				ContextSharingRequest mContextSharingRequest = new ContextSharingRequest(new DeviceIdentifier("A", "1350000000", new GUID("wifi","bluetooth"), mPeerGroupManager.getMyPeerId()), ContextType.ROOM_LIGHT+"rl", new Date().toString(), 1000000);
				Test t = new Test("aaaaa",1);
				try {
					//mContextShareInterface.test(t);
					//mContextShareInterface = mPeerGroupManager.getSignalInterface(GROUP_NAME, mContextShareService,ContextShareInterface.class);
					//mContextShareInterface = mPeerGroupManager.getRemoteObjectInterface(mPeerGroupManager.getGroupHostPeerId(GROUP_NAME),GROUP_NAME, "/ContextShareService", ContextShareInterface.class);
					mContextShareInterface = mPeerGroupManager.getSignalInterface(
							mPeerGroupManager.getGroupHostPeerId(GROUP_NAME),
							GROUP_NAME,
							mContextShareService,
							ContextShareInterface.class);
//					mSignalEmitter = new SignalEmitter(mContextShareService, SignalEmitter.GlobalBroadcast.On);
//					mSignalEmitter.setSessionlessFlag(true);
//					mContextShareInterface = mSignalEmitter.getInterface(ContextShareInterface.class);
					mContextShareInterface.ReceiveContextSharingRequest(mContextSharingRequest);
					//mSignalEmitter.getInterface(ContextShareInterface.class).ReceiveContextSharingRequest(mContextSharingRequest);
				}
				catch (BusException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				break;
			}
			default: {
				Log.i(TAG, "mHandler.handleMessage->default.");
			}

			}
		}
	};

	/* This class will handle all AllJoyn calls. See onBind(). */
	class BusHandler extends Handler {
		// Constants for BusHandler
		private static final int BUS_HANDLE_CONNTECT = 2;
		private static final int BUS_HANDLE_JOIN = 3;
		private static final int BUS_HANDLE_CREATE_SESSION_GROUP = 4;
		private static final int BUS_HANDLE_DISCONNECT = 5;
		private static final int BUS_HANDLE_PEER_CHANGED = 6;
		private static final int BUS_HANDLE_LOST_SESSION = 7;

		public BusHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case BUS_HANDLE_CONNTECT: {
				org.alljoyn.bus.alljoyn.DaemonInit
						.PrepareDaemon(getApplicationContext());
				// Create a BusObject array
				ArrayList<BusObjectData> busObjects = new ArrayList<BusObjectData>();
				busObjects.add(new BusObjectData(mContextShareService,
						"/ContextShareService"));
				// build pgm and configure it
				PeerGroupListener mPeerGroupListener = new PeerGroupListener() {
					@Override
					public void lostAdvertisedName(String groupName,
							short transport) {
						super.lostAdvertisedName(groupName, transport);
						logInfo(String.format(
								"MyPeerGroupListener.lostAdvertisedName"
										+ "(%s, 0x%04x)", groupName, transport));
					}

					@Override
					public void foundAdvertisedName(String groupName,
							short transport) {
						super.foundAdvertisedName(groupName, transport);

						/* If act as a client then try to join the session */
						if (mIsAClient) {
							logInfo(String.format(
									"MyPeerGroupListener.foundAdvertisedName"
											+ "(%s, 0x%04x)", groupName,
									transport));

							/* If found server then stop the thread */
							mIsFoundServer = true;
							mTimerThread.interrupt();
							/* Obtain message to join this session */
							Message om = obtainMessage(
									BusHandler.BUS_HANDLE_JOIN, new String(
											groupName));
							mBusHandler.sendMessage(om);
						}
					}

					@Override
					public void groupLost(String groupName) {
						logInfo(String.format(
								"MyPeerGroupListener.groupLost(%s)", groupName));
						/* Obtain message to lost this session */
						Message om = obtainMessage(
								BusHandler.BUS_HANDLE_LOST_SESSION, new String(
										groupName));
						mBusHandler.sendMessage(om);
					}

					@Override
					public void peerAdded(String peerId, String groupName,
							int numPeers) {
						/* discard when session has lost */
						if (PeerConnectedState.SESSION_LOST == mPeerConnectedState) {
							return;
						}
						super.peerAdded(peerId, groupName, numPeers);
						/* just inform BusHandler to handle it */
						Message om = obtainMessage(
								BusHandler.BUS_HANDLE_PEER_CHANGED, new String(
										groupName));
						mBusHandler.sendMessage(om);
					}

					@Override
					public void peerRemoved(String peerId, String groupName,
							int numPeers) {
						/* discard when session has lost */
						if (groupName == null
								|| PeerConnectedState.SESSION_LOST == mPeerConnectedState) {
							// empty the peers list on main activity
							ArrayList<String> emptylist = new ArrayList<String>();
							updatePeersList(emptylist);
							return;
						}
						super.peerRemoved(peerId, groupName, numPeers);
						/* just inform BusHandler to handle it */
						Message om = obtainMessage(
								BusHandler.BUS_HANDLE_PEER_CHANGED, new String(
										groupName));
						mBusHandler.sendMessage(om);
					}

				};

				// Create the PeerGroupManager
				mPeerGroupManager = new PeerGroupManager(GROUP_PREFIX,
						mPeerGroupListener, busObjects);
				// Show a progress dialog on the main activity
				controlDiscoveringDialog(ControlDiscoveryProgressInstruction.START_PROGRESS_DIALOG);
				break;
			}
			case BUS_HANDLE_JOIN: {
				// Discard this message when already joined the group
				if (PeerConnectedState.JOINED == mPeerConnectedState) {
					return;
				}
				String groupName = (String) msg.obj;
				Status status = mPeerGroupManager.joinGroup(groupName);
				if (status != Status.OK) {
					Log.e(TAG, "Failed to join group - " + status);
					return;
				}
				if (PeerConnectedState.SESSION_LOST == mPeerConnectedState) {
					/*
					 * Update peers list on the main activity (needed when
					 * reconnect to the session)
					 */
					Message om = obtainMessage(
							BusHandler.BUS_HANDLE_PEER_CHANGED, new String(
									groupName));
					mBusHandler.sendMessage(om);
				}
				mPeerConnectedState = PeerConnectedState.JOINED;
				/* Stop the discovering dialog of the main activity */
				controlDiscoveringDialog(ControlDiscoveryProgressInstruction.STOP_PROGRESS_DIALOG);
				updateConnectStatusOnTheActivity();
				updateGroupStatus("Connected to session: " + groupName);
				
				break;
			}
			case BUS_HANDLE_CREATE_SESSION_GROUP: {
				org.alljoyn.bus.Status status = mPeerGroupManager
						.createGroup(GROUP_NAME);
				logStatus(String.format("PeerGroupManager.createGroup%s",
						GROUP_NAME), status);
				if (org.alljoyn.bus.Status.OK == status) {
					mPeerConnectedState = PeerConnectedState.SESSION_HOLDER;
					updateConnectStatusOnTheActivity();
					updateGroupStatus("HOLD session: "
							+ mPeerGroupManager.listHostedGroups().get(0));
					
				} else {
					return;
				}

				break;
			}
			case BUS_HANDLE_DISCONNECT: {
				mPeerGroupManager.cleanup();
				mBusHandler.getLooper().quit();
				break;
			}
			case BUS_HANDLE_LOST_SESSION: {
				String lostgroupName = (String) msg.obj;
				mPeerConnectedState = PeerConnectedState.SESSION_LOST;
				updateConnectStatusOnTheActivity();
				updateGroupStatus("Lost the session: " + lostgroupName);
				break;
			}
			case BUS_HANDLE_PEER_CHANGED: {
				String groupName = (String) msg.obj;
				ArrayList<String> peerList = mPeerGroupManager
						.getPeers(groupName);
				updatePeersList(peerList);
				break;
			}
			default: {
				Log.i(TAG, "BusHandler.handleMessage->default.");
			}
			}
		}

		private void logStatus(String msg, org.alljoyn.bus.Status status) {
			String log = String.format("%s: %s", msg, status);
			if (status == org.alljoyn.bus.Status.OK) {
				Log.i(TAG, log);
			} else {
				Log.e(TAG, log);
			}
		}
	}

	/**
	 * We don't use the bindery to communiate between any client and this
	 * service so we return null.
	 * update by Ray: return the mHandler to communicate with mainactivity
	 */
	@Override
	public IBinder onBind(Intent intent) {
		Log.i(TAG, "onBind()");
		
		Messenger mMessage = new Messenger(mHandler);
		return mMessage.getBinder();
	}

	@Override
	public void onCreate() {
		Log.i(TAG, "onCreate()");
		super.onCreate();
		/*
		 * Make all AllJoyn calls through a separate handler thread to prevent
		 * blocking the UI.
		 */
		HandlerThread busThread = new HandlerThread("BusHandler");
		busThread.start();
		mBusHandler = new BusHandler(busThread.getLooper());

		/* Start our service. */
		mContextShareService = new ContextShareService();
		/* Connect to Bus and check if there is a session already */
		mBusHandler.sendEmptyMessage(BusHandler.BUS_HANDLE_CONNTECT);

		/*
		 * Start a thread to create a session when the above checking process
		 * timeout.
		 */
		mTimerThread = new Thread(new TimerThread());
		mTimerThread.start();

	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "onDestroy()");
		mBusHandler.sendEmptyMessage(BusHandler.BUS_HANDLE_DISCONNECT);
		super.onDestroy();
	}

	/* Update the Connect Status Textview on the main activity */
	public void updateConnectStatusOnTheActivity() {
		Intent updateCSIntent = new Intent();
		String actionName = (String) this.getResources().getText(
				R.string.updateConnectStatusIntentAction);
		String extraName = (String) this.getResources().getText(
				R.string.ConnectStatusName);
		updateCSIntent.setAction(actionName);
		updateCSIntent.putExtra(extraName, mPeerConnectedState);
		sendBroadcast(updateCSIntent);
	}

	/* Control the discovering progress dialog of the main activity */
	public void controlDiscoveringDialog(
			ControlDiscoveryProgressInstruction instruction) {
		Intent cdIntent = new Intent();
		String actionName = (String) this.getResources().getText(
				R.string.controlDiscoveryProgressAction);
		String extraName = (String) this.getResources().getText(
				R.string.DiscoveryProgressInstruction);
		cdIntent.setAction(actionName);
		cdIntent.putExtra(extraName, instruction);
		sendBroadcast(cdIntent);

	}

	/* Update the group status textfield on the main activity */
	public void updateGroupStatus(String group) {
		Intent updateGSIntent = new Intent();
		String actionName = (String) this.getResources().getText(
				R.string.updateGroupStatusAction);
		String extraName = (String) this.getResources().getText(
				R.string.GroupStatusName);
		updateGSIntent.setAction(actionName);
		updateGSIntent.putExtra(extraName, group);
		sendBroadcast(updateGSIntent);
	}

	/* Update Peers List(GroupStatus) when peers changed in the session */
	public void updatePeersList(ArrayList<String> peersList) {
		Intent updatePSIntent = new Intent();
		String actionName = (String) this.getResources().getText(
				R.string.updatePeersListAction);
		String extraName = (String) this.getResources().getText(
				R.string.peersIntentExtraPeersListName);
		updatePSIntent.setAction(actionName);
		updatePSIntent.putExtra(extraName, peersList);
		sendBroadcast(updatePSIntent);
	}
	
	/*Show a toast in the UI activity to show the request message */
	public void showRequestMessage(ContextSharingRequest mContextSharingRequest){
		Intent updateRMIntent = new Intent();
		String actionName = (String) this.getResources().getText(
				R.string.showRequestMessageAction);
		String extraName = (String) this.getResources().getText(
				R.string.requestMessageName);
		updateRMIntent.setAction(actionName);
		updateRMIntent.putExtra(extraName, mContextSharingRequest.getContextType().toString());
		sendBroadcast(updateRMIntent);
		
	}
	
	/*Realize the interface methods*/
	 @BusSignalHandler(iface = "cn.edu.pku.ss.situationperceiver.ContextShareInterface", signal = "ReceiveContextSharingRequest")
	 public void ReceiveContextSharingRequest(ContextSharingRequest mContextSharingRequest) throws BusException {
		 
		 logInfo("ReceiveContextShareingRequest: "+mContextSharingRequest.getContextType().toString());
		 
		 /*ignore when senderid==itself*/
		 if(mPeerGroupManager.getSenderPeerId().equals(mPeerGroupManager.getMyPeerId()))
			 return;
		 
		 //do something
		 
		 Message csrm = mHandler.obtainMessage(HANDLE_RECEIVE_INTEREST, mContextSharingRequest);
		 mHandler.sendMessage(csrm);
	}
	 
	 @BusSignalHandler(iface = "cn.edu.pku.ss.situationperceiver.ContextShareInterface", signal = "ReceiveContextMessage")
	 public void ReceiveContextMessage(ContextMessage mContextMessage) throws BusException {
		 /*ignore when senderid==myself*/
		 if(mPeerGroupManager.getSenderPeerId().equals(mPeerGroupManager.getMyPeerId()))
			 return;
		 
		 /*ignore when recipientid!=myself*/
		 if(!mContextMessage.getRecipientID().getPeerid().equals(mPeerGroupManager.getMyPeerId()))
			 return;
		
		 //do something
		 
		 mHandler.sendEmptyMessage(HANDLE_SHARE_CONTEXT);
	}
	 
	 @BusSignalHandler(iface = "cn.edu.pku.ss.situationperceiver.ContextShareInterface", signal = "test")
	 public void test(Test t) throws BusException {
		 
		 
		 logInfo("test : "+t.testString);
		 
		 /*ignore when senderid==myself*/
		 if(mPeerGroupManager.getSenderPeerId().equals(mPeerGroupManager.getMyPeerId()))
			 return;

	}
	

	private void logInfo(String msg) {
		Log.i(TAG, msg);
	}

	/* Define a thread for trigger timeout when check available sessions. */
	public class TimerThread implements Runnable {
		@Override
		public void run() {
			try {
				/* Sleep 5000ms to search if there is a server */
				Thread.sleep(5000);

				if (!mIsFoundServer) {
					mPeerConnectedState = PeerConnectedState.DISCONNECTED;
					/* Stop the discovering dialog of the main activity */
					controlDiscoveringDialog(ControlDiscoveryProgressInstruction.STOP_PROGRESS_DIALOG);
					updateConnectStatusOnTheActivity();
					updateGroupStatus("Stop connecting.Now build up a server!");
					/* Turn off the listener listening the advertised group */
					mIsAClient = false;
					/* Create a new session group */
					mBusHandler
							.sendEmptyMessage(BusHandler.BUS_HANDLE_CREATE_SESSION_GROUP);
				}
			} catch (Exception e) {
			}

		}
	}


}
